﻿namespace Legend.Web.Tests
{
    using System.Collections.Generic;
    using System.Linq;
    using Web;
    using NUnit.Framework;

    [TestFixture]
    public class QueryStringConverterTests
    {
        [Test]
        public void Should_parse_to_empty_dictionary_when_query_is_empty()
        {
            // Arrange

            // Act
            var result = this.Parse(string.Empty);

            // Assert
            Assert.That(result.Count, Is.EqualTo(0));
        }

        [Test]
        public void Should_associate_key_with_no_value_with_empty_collection_when_parsing()
        {
            // Arrange

            // Act
            var result = this.Parse("foo=");

            // Assert
            Assert.That(result["foo"], Is.Empty);
        }

        [Test]
        public void Should_associate_key_with_no_value_or_equals_sign_with_empty_collection_when_parsing()
        {
            // Arrange

            // Act
            var result = this.Parse("foo");

            // Assert
            Assert.That(result["foo"], Is.Empty);
        }

        [Test]
        public void Should_associate_key_with_value_when_parsing()
        {
            // Arrange

            // Act
            var result = this.Parse("foo=bar");

            // Assert
            Assert.That(result["foo"], Is.EquivalentTo(Sequence("bar")));
        }

        [Test]
        public void Should_associate_multiple_keys_and_values()
        {
            // Arrange

            // Act
            var result = this.Parse("foo=bar&baz=biz");

            // Assert
            Assert.That(result["foo"], Is.EquivalentTo(Sequence("bar")));
            Assert.That(result["baz"], Is.EquivalentTo(Sequence("biz")));
        }

        [Test]
        public void Should_associate_multiple_keys_and_values_separated_by_semicolons()
        {
            // Arrange

            // Act
            var result = this.Parse("foo=bar;baz=biz");

            // Assert
            Assert.That(result["foo"], Is.EquivalentTo(Sequence("bar")));
            Assert.That(result["baz"], Is.EquivalentTo(Sequence("biz")));
        }

        [Test]
        public void Should_associate_multiple_entries_for_same_key_on_same_key_when_parsing()
        {
            // Arrange

            // Act
            var result = this.Parse("foo=bar&foo=baz");

            // Assert
            Assert.That(result["foo"], Is.EquivalentTo(Sequence("bar", "baz")));
        }

        [Test]
        public void Should_split_multiple_values_separated_by_commas_when_parsing()
        {
            // Arrange

            // Act
            var result = this.Parse("foo=bar,baz");

            // Assert
            Assert.That(result["foo"], Is.EquivalentTo(Sequence("bar", "baz")));
        }

        [Test]
        public void Should_decode_encoded_values()
        {
            // Arrange

            // Act
            var result = this.Parse("foo=b+r");

            // Assert
            Assert.That(result["foo"], Is.EquivalentTo(Sequence("b r")));
        }

        [Test]
        public void Should_decode_encoded_keys()
        {
            // Arrange

            // Act
            var result = this.Parse("f%3ao=");

            // Assert
            Assert.That(result.Keys, Has.Some.EqualTo("f:o"));
        }

        private IEnumerable<ToStringTestCase> ToStringCases
        {
            get
            {
                yield return new ToStringTestCase()
                             {
                                 Dictionary = new Dictionary<string, IEnumerable<string>>(),
                                 ExpectedResult = string.Empty
                             };
                yield return new ToStringTestCase()
                             {
                                 Dictionary = new Dictionary<string, IEnumerable<string>>()
                                              {
                                                  { "foo", Enumerable.Empty<string>() },
                                                  { "bar", Enumerable.Empty<string>() }
                                              },
                                 ExpectedResult = "foo=&bar="
                             };
                yield return new ToStringTestCase()
                             {
                                 Dictionary = new Dictionary<string, IEnumerable<string>>()
                                              {
                                                  { "foo", Sequence("bar") },
                                                  { "bar", Sequence("foo") }
                                              },
                                 ExpectedResult = "foo=bar&bar=foo"
                             };
                yield return new ToStringTestCase()
                             {
                                 Dictionary = new Dictionary<string, IEnumerable<string>>()
                                              {
                                                  { "foo", Sequence("bar", "baz") },
                                                  { "bar", Sequence("foo", "biz") }
                                              },
                                 ExpectedResult = "foo=bar&foo=baz&bar=foo&bar=biz"
                             };
                yield return new ToStringTestCase()
                             {
                                 Dictionary = new Dictionary<string, IEnumerable<string>>()
                                              {
                                                  {"f o", Sequence("b r")}
                                              },
                                 ExpectedResult = "f+o=b+r"
                             };
            }
        }

        public class ToStringTestCase
        {
            public IDictionary<string, IEnumerable<string>> Dictionary { get; set; }

            public string ExpectedResult { get; set; }
        }

        [TestCaseSource("ToStringCases")]
        public void Should_print_string_correctly(ToStringTestCase testCase)
        {
            // Arrange
            var converter = new QueryStringConverter();

            // Act
            string result = converter.ToString(testCase.Dictionary);

            // Assert
            Assert.That(result, Is.EqualTo(testCase.ExpectedResult));
        }

        private static IEnumerable<T> Sequence<T>(params T[] values)
        {
            return values;
        }

        private IDictionary<string, IEnumerable<string>> Parse(string queryString)
        {
            return new QueryStringConverter().Parse(queryString);
        }
    }
}